package com.bowlong.net.http;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/***
 * URI utilities.
 *
 * @version $Revision: 991189 $
 */
public final class URISupport {

	private static final String CHARSET = "UTF-8";

	private URISupport() {
		// Helper class
	}

	public static Map<String, Object> parseQuery(String uri)
			throws URISyntaxException {
		// must check for trailing & as the uri.split("&") will ignore those
		if (uri != null && uri.endsWith("&")) {
			throw new URISyntaxException(uri,
					"Invalid uri syntax: Trailing & marker found. "
							+ "Check the uri and remove the trailing & marker.");
		}

		try {
			// use a linked map so the parameters is in the same order
			Map<String, Object> rc = new LinkedHashMap<String, Object>();
			if (uri != null) {
				String[] parameters = uri.split("&");
				for (String parameter : parameters) {
					int p = parameter.indexOf("=");
					if (p >= 0) {
						String name = URLDecoder.decode(
								parameter.substring(0, p), CHARSET);
						String value = URLDecoder.decode(
								parameter.substring(p + 1), CHARSET);
						rc.put(name, value);
					} else {
						rc.put(parameter, null);
					}
				}
			}
			return rc;
		} catch (UnsupportedEncodingException e) {
			URISyntaxException se = new URISyntaxException(e.toString(),
					"Invalid encoding");
			se.initCause(e);
			throw se;
		}
	}

	public static Map<String, Object> parseParameters(URI uri)
			throws URISyntaxException {
		String query = uri.getQuery();
		if (query == null) {
			String schemeSpecificPart = uri.getSchemeSpecificPart();
			int idx = schemeSpecificPart.indexOf('?');
			if (idx < 0) {
				// return an empty map
				return new LinkedHashMap<String, Object>(0);
			} else {
				query = schemeSpecificPart.substring(idx + 1);
			}
		} else {
			query = stripPrefix(query, "?");
		}
		return parseQuery(query);
	}

	/***
	 * Creates a URI with the given query
	 */
	public static URI createURIWithQuery(URI uri, String query)
			throws URISyntaxException {
		return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(),
				uri.getPort(), uri.getPath(), query, uri.getFragment());
	}

	public static String stripPrefix(String value, String prefix) {
		if (value.startsWith(prefix)) {
			return value.substring(prefix.length());
		}
		return value;
	}

	public static String createQueryString(Map<Object, Object> options)
			throws URISyntaxException {
		try {
			if (options.size() > 0) {
				StringBuilder rc = new StringBuilder();
				boolean first = true;
				for (Object o : options.keySet()) {
					if (first) {
						first = false;
					} else {
						rc.append("&");
					}

					String key = (String) o;
					String value = (String) options.get(key);
					rc.append(URLEncoder.encode(key, CHARSET));
					// only append if value is not null
					if (value != null) {
						rc.append("=");
						rc.append(URLEncoder.encode(value, CHARSET));
					}
				}
				return rc.toString();
			} else {
				return "";
			}
		} catch (UnsupportedEncodingException e) {
			URISyntaxException se = new URISyntaxException(e.toString(),
					"Invalid encoding");
			se.initCause(e);
			throw se;
		}
	}

	/***
	 * Creates a URI from the original URI and the remaining parameters
	 * <p/>
	 * Used by various Camel components
	 */
	public static URI createRemainingURI(URI originalURI,
			Map<Object, Object> params) throws URISyntaxException {
		String s = createQueryString(params);
		if (s.length() == 0) {
			s = null;
		}
		return createURIWithQuery(originalURI, s);
	}

	/***
	 * Normalizes the uri by reordering the parameters so they are sorted and
	 * thus we can use the uris for endpoint matching.
	 *
	 * @param uri
	 *            the uri
	 * @return the normalized uri
	 * @throws URISyntaxException
	 *             in thrown if the uri syntax is invalid
	 */
	public static String normalizeUri(String uri) throws URISyntaxException {
		URI u = new URI(UnsafeUriCharactersEncoder.encode(uri));
		String path = u.getSchemeSpecificPart();
		String scheme = u.getScheme();

		// not possible to normalize
		if (scheme == null || path == null) {
			return uri;
		}

		// lets trim off any query arguments
		if (path.startsWith("//")) {
			path = path.substring(2);
		}
		int idx = path.indexOf('?');
		if (idx > 0) {
			path = path.substring(0, idx);
		}

		// in case there are parameters we should reorder them
		Map<String, Object> parameters = URISupport.parseParameters(u);
		if (parameters.isEmpty()) {
			// no parameters then just return
			return buildUri(scheme, path, null);
		} else {
			// reorder parameters a..z
			List<String> keys = new ArrayList<String>(parameters.keySet());
			Collections.sort(keys);

			Map<Object, Object> sorted = new LinkedHashMap<Object, Object>(
					parameters.size());
			for (String key : keys) {
				sorted.put(key, parameters.get(key));
			}

			// build uri object with sorted parameters
			String query = URISupport.createQueryString(sorted);
			return buildUri(scheme, path, query);
		}
	}

	private static String buildUri(String scheme, String path, String query) {
		// must include :// to do a correct URI all components can work with
		return scheme + "://" + path + (query != null ? "?" + query : "");
	}
}
